#include "Cluster.h"
#include "Cluster_Metric.h"
#include "Log.h"
#include "Accumulator.h"
#include "Accumulator_Max.h"
#include "Accumulator_Min.h"
#include "Accumulator_Sum.h"
#include "Iterators.h"

Cluster_Metric::Cluster_Metric(Cluster_Distance::Type distance_type,
                               Representative_Set::Type representatives) {
    this->distance_type = distance_type;
    this->representatives = representatives;
}

double Cluster_Metric::distance(Cluster *A, Cluster *B) {
    switch (distance_type) {
    case Cluster_Distance::AVERAGE:
        return average_link_distance(A, B);

    case Cluster_Distance::SINGLE:
        return single_link_distance(A, B);

    case Cluster_Distance::COMPLETE:
        return complete_link_distance(A, B);
    }
}

// Accumulate a distance between two clusters in several different ways
// This does the bulk of the work.

void Cluster_Metric::accumulate_distance(Cluster *A, Cluster *B,
                                         Accumulator& accumulator) {

    LOG_ENTER(OBJECT,"Cluster_Metric::accumulate_distance");

    Pile *A_Set, *B_Set;

    switch (representatives) {
    case Representative_Set::WHOLE:
        A_Set = A->items;
        B_Set = B->items;
        break;

    case Representative_Set::EXTERIOR:
        A_Set = A->extreme_set;
        B_Set = B->extreme_set;
        break;

    case Representative_Set::INTERIOR:
        A_Set = A->interior_set;
        B_Set = B->interior_set;
        break;

    case Representative_Set::MEDOID:
        A_Set = A->medoid;
        B_Set = B->medoid;
        break;
    }

    Iterator<Item*> *a_iter = A_Set->new_iterator();
    while (a_iter->has_next()) {
        Item *a_item = a_iter->get_next();
        Iterator<Item*> *b_iter = B_Set->new_iterator();
        while (b_iter->has_next()) {
            Item *b_item = b_iter->get_next();
            double dist = a_item->distance(b_item);
            accumulator.accumulate(dist);
        }
        delete b_iter;
    }
    delete a_iter;

    LOG_EXIT();
}

// Distance between two clusters, using single-link (shortest bridge) approach.

double Cluster_Metric::single_link_distance(Cluster *A, Cluster *B) {
    LOG_ENTER(OBJECT,"Cluster_Metric::single_link_distance");

    Accumulator_Min min_acc;
    accumulate_distance(A, B, min_acc);

    LOG_EXIT();

    return min_acc.value;
}

// Distance between two clusters, using complete-link (diameter) approach.

double Cluster_Metric::complete_link_distance(Cluster *A, Cluster *B) {
    LOG_ENTER(OBJECT, "Cluster_Metric::complete_link_distance");

    Accumulator_Max max_acc;
    accumulate_distance(A, B, max_acc);

    LOG(OBJECT) << "max=" << max_acc.value << " count=" << max_acc.count;
    LOG_EXIT();

    return max_acc.value;
}

// Distance between two clusters, using average-link approach.

double Cluster_Metric::average_link_distance(Cluster *A, Cluster *B) {
    LOG_ENTER(OBJECT,"Cluster_Metric::average_link_distance");

    Accumulator_Sum sum_acc;
    accumulate_distance(A, B, sum_acc);

    LOG(OBJECT) << "sum=" << sum_acc.value << " count=" << sum_acc.count;

    double result;
    if (sum_acc.count == 0)
        result = 0;
    else
        result = sum_acc.value / sum_acc.count;

    LOG_EXIT();

    return result;
}
